﻿using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace B.S.BaseData.Infrastructure.Helper
{
    public class ExportToExcelHelper
    {
        /// <summary>
        /// 通用数据导出方法
        /// </summary>
        /// <typeparam name="T">要导出的数据类型</typeparam>
        /// <param name="data">数据集合</param>
        /// <param name="sheetName">工作表名称</param>
        /// <param name="fileName">文件名(不含扩展名)</param>
        /// <param name="excludeProperties">要排除的属性名称集合</param>
        /// <returns>文件字节数组</returns>
        public byte[] ExportToExcel<T>(IEnumerable<T> data, string sheetName = "Sheet1",
            string fileName = "ExportData", string[]? excludeProperties = null) where T : class
        {
            try
            {
                var workbook = new XSSFWorkbook();
                var sheet = workbook.CreateSheet(sheetName);

                #region 创建标题行样式
                var headerStyle = workbook.CreateCellStyle();
                var headerFont = workbook.CreateFont();
                headerFont.FontHeightInPoints = 12; // 字体大小
                headerFont.IsBold = true; // 加粗
                headerStyle.SetFont(headerFont);
                headerStyle.Alignment = HorizontalAlignment.Center; // 居中对齐
                headerStyle.VerticalAlignment = VerticalAlignment.Center;
                headerStyle.FillForegroundColor = IndexedColors.Grey25Percent.Index;
                headerStyle.FillPattern = FillPattern.SolidForeground;
                headerStyle.BorderTop = BorderStyle.Thin;
                headerStyle.BorderBottom = BorderStyle.Thin;
                headerStyle.BorderLeft = BorderStyle.Thin;
                headerStyle.BorderRight = BorderStyle.Thin;
                #endregion 

                #region 创建内容样式
                var contentStyle = workbook.CreateCellStyle();
                var contentFont = workbook.CreateFont();
                contentFont.FontHeightInPoints = 11;
                contentStyle.SetFont(contentFont);
                contentStyle.Alignment = HorizontalAlignment.Left;
                contentStyle.VerticalAlignment = VerticalAlignment.Center;
                contentStyle.BorderTop = BorderStyle.Thin;
                contentStyle.BorderBottom = BorderStyle.Thin;
                contentStyle.BorderLeft = BorderStyle.Thin;
                contentStyle.BorderRight = BorderStyle.Thin;
                #endregion 

                // 获取属性信息
                var properties = typeof(T).GetProperties()
                    .Where(p => excludeProperties == null || !excludeProperties.Contains(p.Name))
                    .ToList();

                // 创建表头
                var headerRow = sheet.CreateRow(0);
                headerRow.Height = 400; // 设置行高
                for (int i = 0; i < properties.Count; i++)
                {
                    var cell = headerRow.CreateCell(i);
                    // 获取DisplayName特性或属性名
                    var displayName = properties[i].GetCustomAttribute<DisplayNameAttribute>()?.DisplayName
                        ?? properties[i].Name;
                    cell.SetCellValue(displayName);
                    cell.CellStyle = headerStyle;
                }

                // 填充数据
                int rowIndex = 1;
                foreach (var item in data)
                {
                    var row = sheet.CreateRow(rowIndex);
                    row.Height = 350; // 设置行高

                    for (int i = 0; i < properties.Count; i++)
                    {
                        var cell = row.CreateCell(i);
                        var value = properties[i].GetValue(item);

                        // 根据属性类型设置单元格值
                        if (value != null)
                        {
                            if (properties[i].PropertyType == typeof(DateTime) ||
                                properties[i].PropertyType == typeof(DateTime?))
                            {
                                cell.SetCellValue(((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss"));
                            }
                            else if (properties[i].PropertyType == typeof(bool) ||
                                     properties[i].PropertyType == typeof(bool?))
                            {
                                cell.SetCellValue((bool)value ? "是" : "否");
                            }
                            else if (properties[i].PropertyType.IsEnum)
                            {
                                cell.SetCellValue(Enum.GetName(properties[i].PropertyType, value));
                            }
                            else
                            {
                                cell.SetCellValue(value.ToString());
                            }
                        }
                        cell.CellStyle = contentStyle;
                    }
                    rowIndex++;
                }

                // 自动调整列宽
                for (int i = 0; i < properties.Count; i++)
                {
                    sheet.AutoSizeColumn(i);
                    // 设置最小列宽
                    if (sheet.GetColumnWidth(i) < 15 * 256)
                    {
                        sheet.SetColumnWidth(i, 15 * 256);
                    }
                    // 设置最大列宽
                    if (sheet.GetColumnWidth(i) > 50 * 256)
                    {
                        sheet.SetColumnWidth(i, 50 * 256);
                    }
                }

                // 导出excel
                using (var ms = new MemoryStream())
                {
                    workbook.Write(ms);
                    return ms.ToArray();
                }
            }
            catch (Exception ex)
            {
                throw;
            }
        }



        public byte[] ExportToExcelMy<T>(List<T> data, string sheetName = "Sheet1")
        {
            // 1. 创建一个新的 Excel 工作簿，这里使用 XSSFWorkbook 表示 .xlsx 格式
            var workbook = new XSSFWorkbook();
            // 2. 在工作簿中创建一个新的工作表，并指定其名称
            var sheet = workbook.CreateSheet(sheetName);

            #region 创建标题行样式
            // 创建一个单元格样式对象，用于设置表头的样式
            var headerStyle = workbook.CreateCellStyle();
            // 创建一个字体对象，用于设置表头文本的字体
            var headerFont = workbook.CreateFont();
            // 设置表头字体大小为 12 
            headerFont.FontHeightInPoints = 12;
            // 设置表头字体加粗
            headerFont.IsBold = true;
            // 将字体应用到表头样式
            headerStyle.SetFont(headerFont);
            // 设置表头文本水平居中对齐
            headerStyle.Alignment = HorizontalAlignment.Center;
            // 设置表头文本垂直居中对齐
            headerStyle.VerticalAlignment = VerticalAlignment.Center;
            // 设置表头单元格的背景填充前景色为 25% 灰色
            headerStyle.FillForegroundColor = IndexedColors.Grey25Percent.Index;
            // 设置表头单元格的填充模式为纯色填充
            headerStyle.FillPattern = FillPattern.SolidForeground;
            // 设置表头单元格上边框为细线
            headerStyle.BorderTop = BorderStyle.Thin;
            // 设置表头单元格下边框为细线
            headerStyle.BorderBottom = BorderStyle.Thin;
            // 设置表头单元格左边框为细线
            headerStyle.BorderLeft = BorderStyle.Thin;
            // 设置表头单元格右边框为细线
            headerStyle.BorderRight = BorderStyle.Thin;
            #endregion

            #region 创建内容样式
            // 创建一个单元格样式对象，用于设置内容区域的样式
            var contentStyle = workbook.CreateCellStyle();
            // 创建一个字体对象，用于设置内容文本的字体
            var contentFont = workbook.CreateFont();
            // 设置内容字体大小为 11 磅
            contentFont.FontHeightInPoints = 11;
            // 将字体应用到内容样式
            contentStyle.SetFont(contentFont);
            // 设置内容文本水平左对齐
            contentStyle.Alignment = HorizontalAlignment.Left;
            // 设置内容文本垂直居中对齐
            contentStyle.VerticalAlignment = VerticalAlignment.Center;
            // 设置内容单元格上边框为细线
            contentStyle.BorderTop = BorderStyle.Thin;
            // 设置内容单元格下边框为细线
            contentStyle.BorderBottom = BorderStyle.Thin;
            // 设置内容单元格左边框为细线
            contentStyle.BorderLeft = BorderStyle.Thin;
            // 设置内容单元格右边框为细线
            contentStyle.BorderRight = BorderStyle.Thin;
            #endregion

            // 获取类型 T 的所有公共属性，并过滤掉在 excludeProperties 列表中指定的属性
            var properties = typeof(T).GetProperties().ToList();
            /*.Where(p => excludeProperties == null || !excludeProperties.Contains(p.Name))*/


            // 创建第一行（索引为 0）作为表头行
            var headerRow = sheet.CreateRow(0);
            // 设置表头行高，单位是 twip（1 twip = 1/20 磅），400 twip = 20 磅
            headerRow.Height = 400;
            // 遍历所有要导出的属性，创建表头单元格
            for (int i = 0; i < properties.Count; i++)
            {
                // 在当前表头行创建第 i 个单元格
                var cell = headerRow.CreateCell(i);
                // 获取属性的 DisplayName 特性值，如果不存在则使用属性本身的名称作为列名
                var displayName = properties[i].GetCustomAttribute<DisplayNameAttribute>()?.DisplayName
                    ?? properties[i].Name;
                // 设置单元格的值为显示名称
                cell.SetCellValue(displayName);
                // 将之前定义的表头样式应用到当前单元格
                cell.CellStyle = headerStyle;
            }

            // 初始化行索引，从第二行（索引为 1）开始填充数据
            int rowIndex = 1;
            // 遍历数据列表中的每一个数据项
            foreach (var item in data)
            {
                // 在当前工作表创建新的数据行
                var row = sheet.CreateRow(rowIndex);
                // 设置数据行高，单位是 twip，350 twip = 17.5 磅
                row.Height = 350;

                // 遍历当前数据项的所有属性，填充单元格数据
                for (int i = 0; i < properties.Count; i++)
                {
                    // 在当前数据行创建第 i 个单元格
                    var cell = row.CreateCell(i);
                    // 获取当前属性在当前数据项中的值
                    var value = properties[i].GetValue(item);

                    // 根据属性类型设置单元格的值，以确保正确的格式
                    if (value != null) // 确保值不为空
                    {
                        // 如果是 DateTime 或可空的 DateTime 类型
                        if (properties[i].PropertyType == typeof(DateTime) ||
                            properties[i].PropertyType == typeof(DateTime?))
                        {
                            // 将日期时间格式化为 "yyyy-MM-dd HH:mm:ss" 字符串
                            cell.SetCellValue(((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss"));
                        }
                        // 如果是 bool 或可空的 bool 类型
                        else if (properties[i].PropertyType == typeof(bool) ||
                                 properties[i].PropertyType == typeof(bool?))
                        {
                            // 根据布尔值设置为 "是" 或 "否"
                            cell.SetCellValue((bool)value ? "是" : "否");
                        }
                        // 如果是枚举类型
                        else if (properties[i].PropertyType.IsEnum)
                        {
                            // 获取枚举值的名称（字符串形式）
                            cell.SetCellValue(Enum.GetName(properties[i].PropertyType, value));
                        }
                        // 对于其他所有类型
                        else
                        {
                            // 将值转换为字符串设置到单元格
                            cell.SetCellValue(value.ToString());
                        }
                    }
                    // 将之前定义的内容样式应用到当前单元格
                    cell.CellStyle = contentStyle;
                }
                // 行索引递增，准备处理下一行数据
                rowIndex++;
            }

            // 遍历所有列，自动调整列宽并设置最小/最大限制
            for (int i = 0; i < properties.Count; i++)
            {
                // 自动调整当前列的宽度以适应其内容
                sheet.AutoSizeColumn(i);
                // 设置最小列宽：如果自动调整后的宽度小于 15 个字符宽（15 * 256 twip），则设置为 15 个字符宽
                if (sheet.GetColumnWidth(i) < 15 * 256)
                {
                    sheet.SetColumnWidth(i, 15 * 256);
                }
                // 设置最大列宽：如果自动调整后的宽度大于 50 个字符宽（50 * 256 twip），则限制为 50 个字符宽
                if (sheet.GetColumnWidth(i) > 50 * 256)
                {
                    sheet.SetColumnWidth(i, 50 * 256);
                }
            }

            // 使用 MemoryStream 将工作簿内容写入内存
            using (var ms = new MemoryStream())
            {
                // 将整个工作簿的数据写入内存流
                workbook.Write(ms);
                // 将内存流中的数据转换为字节数组并返回
                return ms.ToArray();
            }
        }






    }
}
